package com.xyzwps.server.paimon.it

import com.xyzwps.server.paimon.common.CursorPage
import com.xyzwps.server.paimon.ui.view.ArticleView
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.server.LocalServerPort
import org.springframework.core.ParameterizedTypeReference
import java.util.function.Consumer

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class GraphqlArticleTests(@LocalServerPort port: Int) : GqlTestBase(port) {

    companion object {
        val createArticleDocument = $$"""
            mutation ArticleCreate ($input: ArticleCreateInput!) {
                articleCreate(input: $input)
            }
        """.trimIndent()

        val updateArticleDocument = $$"""
            mutation ArticleUpdate ($uid: String!, $input: ArticleUpdateInput!) {
                articleUpdate(uid: $uid, input: $input)
            }
        """.trimIndent()

        val getArticleDocument = $$"""
            query ArticleGet ($uid: String!) {
                article(uid: $uid) {
                    uid
                    title
                    content
                    createdAt
                    author {
                        nickname
                    }
                }
            }
        """.trimIndent()

        val getArticlesDocument = $$"""
            query ArticlesGet ($input: ArticlesGetInput!) {
                articles(input: $input) {
                    items {
                        uid
                        title
                        content
                        createdAt
                        author {
                            nickname
                        }
                    }
                    nextCursor
                    hasMore
                }
            }
        """.trimIndent()
    }

    private class EnvCursor {
        var cursor: String? = null
    }

    @Test
    fun createArticleWithoutTokenTest() {
        graphQlTester.document(createArticleDocument)
            .variable("input", mapOf("title" to "t", "content" to "c"))
            .execute()
            .errors()
            .satisfy { errors ->
                assertEquals(1, errors.size)
                val error = errors.first()
                assertEquals("UNAUTHORIZED", error.extensions["title"])
                assertEquals("/errors/100", error.extensions["type"])
                assertEquals(401, error.extensions["status"])
            }
    }

    fun createArticle(token: String, title: String, content: String): String {
        return graphQlTester.mutate()
            .headers { headers -> headers.setBearerAuth(token) }
            .build()
            .document(createArticleDocument)
            .variable("input", mapOf("title" to title, "content" to content))
            .execute()
            .path("articleCreate")
            .entity(String::class.java)
            .get()
    }

    @Test
    fun updateArticleWithoutTokenTest() {
        graphQlTester.document(updateArticleDocument)
            .variable("uid", "xx.kt")
            .variable("input", mapOf("title" to "t", "content" to "c"))
            .execute()
            .errors()
            .satisfy { errors ->
                assertEquals(1, errors.size)
                val error = errors.first()
                assertEquals("UNAUTHORIZED", error.extensions["title"])
                assertEquals("/errors/100", error.extensions["type"])
            }
    }

    fun updateArticle(token: String, uid: String, title: String, content: String): String {
        return graphQlTester.mutate()
            .headers { headers -> headers.setBearerAuth(token) }
            .build()
            .document(updateArticleDocument)
            .variable("uid", uid)
            .variable("input", mapOf("title" to title, "content" to content))
            .execute()
            .path("articleUpdate")
            .entity(String::class.java)
            .get()
    }

    @Test
    fun getArticleWithoutTokenTest() {
        graphQlTester.document(getArticleDocument)
            .variable("uid", "xx.kt")
            .execute()
            .errors()
            .satisfy { errors ->
                assertEquals(1, errors.size)
                val error = errors.first()
                assertEquals("NOT_FOUND", error.extensions["title"])
                assertEquals("/errors/5001", error.extensions["type"])
            }
    }

    fun getArticle(token: String, uid: String, asserter: Consumer<ArticleView>) {
        graphQlTester.mutate()
            .headers { headers -> headers.setBearerAuth(token) }
            .build()
            .document(getArticleDocument)
            .variable("uid", uid)
            .execute()
            .path("article")
            .entity(ArticleView::class.java)
            .satisfies(asserter)
    }

    @Test
    fun getArticlesWithoutTokenTest() {
        graphQlTester.document(getArticlesDocument)
            .variable("input", mutableMapOf("count" to 10))
            .execute()
            .errors()
            .satisfy { errors ->
                assertEquals(1, errors.size)
                val error = errors.first()
                assertEquals("UNAUTHORIZED", error.extensions["title"])
                assertEquals("/errors/100", error.extensions["type"])
            }
    }

    fun getArticles(
        token: String,
        cursor: String?,
        count: Int,
        asserter: Consumer<CursorPage<ArticleView, String>>
    ) {
        graphQlTester.mutate()
            .headers { headers -> headers.setBearerAuth(token) }
            .build()
            .document(getArticlesDocument)
            .variable("input", mutableMapOf("count" to count, "cursor" to cursor))
            .execute()
            .path("articles")
            .entity(object : ParameterizedTypeReference<CursorPage<ArticleView, String>>() {})
            .satisfies(asserter)
    }

    @Test
    fun createAndGetArticleTest() {
        val (me, token) = registerAndLogin()
        val limit = 3

        val uid1 = createArticle(token, "测试文章1", "测试内容1")
        val a1Asserter = Consumer<ArticleView> { a ->
            assertNull(a.id)
            assertEquals(uid1, a.uid)
            assertNull(a.userId)
            assertEquals("测试文章1", a.title)
            assertEquals("测试内容1", a.content)
            assertNotNull(a.createdAt)

            assertNotNull(a.author)
            assertNull(a.author?.id)
            assertEquals(me.nickname, a.author?.nickname)
        }
        getArticle(token, uid1, a1Asserter)
        getArticles(token, null, limit) { page ->
            assertEquals(1, page.items.size)
            assertNull(page.nextCursor)
            a1Asserter.accept(page.items.first())
        }

        val uid2 = createArticle(token, "测试文章2", "测试内容2")
        val a2Asserter = Consumer<ArticleView> { a ->
            assertNull(a.id)
            assertEquals(uid2, a.uid)
            assertNull(a.userId)
            assertEquals("测试文章2", a.title)
            assertEquals("测试内容2", a.content)
            assertNotNull(a.createdAt)

            assertNotNull(a.author)
            assertNull(a.author?.id)
            assertEquals(me.nickname, a.author?.nickname)
        }
        getArticle(token, uid2, a2Asserter)
        getArticles(token, null, limit) { page ->
            assertEquals(2, page.items.size)
            assertNull(page.nextCursor)

            a2Asserter.accept(page.items.first())
            a1Asserter.accept(page.items[1])
        }

        val uid3 = createArticle(token, "测试文章3", "测试内容3")
        val a3Asserter = Consumer<ArticleView> { a ->
            assertNull(a.id)
            assertEquals(uid3, a.uid)
            assertNull(a.userId)
            assertEquals("测试文章3", a.title)
            assertEquals("测试内容3", a.content)
            assertNotNull(a.createdAt)

            assertNotNull(a.author)
            assertNull(a.author?.id)
            assertEquals(me.nickname, a.author?.nickname)
        }
        getArticle(token, uid3, a3Asserter)
        getArticles(token, null, limit) { page ->
            assertEquals(3, page.items.size)
            assertNull(page.nextCursor)

            a3Asserter.accept(page.items.first())
            a2Asserter.accept(page.items[1])
            a1Asserter.accept(page.items[2])
        }

        val uid4 = createArticle(token, "测试文章4", "测试内容4")
        val a4Asserter = Consumer<ArticleView> { a ->
            assertNull(a.id)
            assertEquals(uid4, a.uid)
            assertNull(a.userId)
            assertEquals("测试文章4", a.title)
            assertEquals("测试内容4", a.content)
            assertNotNull(a.createdAt)

            assertNotNull(a.author)
            assertNull(a.author?.id)
            assertEquals(me.nickname, a.author?.nickname)
        }
        getArticle(token, uid4, a4Asserter)
        val env = EnvCursor()
        getArticles(token, null, limit) { page ->
            assertEquals(3, page.items.size)
            assertNotNull(page.nextCursor)
            env.cursor = page.nextCursor

            a4Asserter.accept(page.items.first())
            a3Asserter.accept(page.items[1])
            a2Asserter.accept(page.items[2])
        }

        getArticles(token, env.cursor, limit) { page ->
            assertEquals(1, page.items.size)
            assertNull(page.nextCursor)

            a1Asserter.accept(page.items.first())
        }
    }

    @Test
    fun createAndUpdateArticleTest() {
        val (me, token) = registerAndLogin()
        val uid = createArticle(token, "测试文章1", "测试内容1")
        getArticle(token, uid) { a ->
            assertNull(a.id)
            assertEquals(uid, a.uid)
            assertNull(a.userId)
            assertEquals("测试文章1", a.title)
            assertEquals("测试内容1", a.content)
            assertNotNull(a.createdAt)

            assertNotNull(a.author)
            assertNull(a.author?.id)
            assertEquals(me.nickname, a.author?.nickname)
        }

        updateArticle(token, uid, "测试文章1-修改", "测试内容1-修改")
        getArticle(token, uid) { a ->
            assertNull(a.id)
            assertEquals(uid, a.uid)
            assertNull(a.userId)
            assertEquals("测试文章1-修改", a.title)
            assertEquals("测试内容1-修改", a.content)
            assertNotNull(a.createdAt)

            assertNotNull(a.author)
            assertNull(a.author?.id)
            assertEquals(me.nickname, a.author?.nickname)
        }
    }
}

